package edu.thu.thss.yxy.util;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Random;

/**
 * Utility methods and fields to use when working with network addresses.
 * 
 * @author Andy
 * 
 */
public class NetworkUtil {
	private static final Logger logger = Logger.getLogger(NetworkUtil.class);

	/**
	 * A string containing the "any" local address for IPv6.
	 */
	public static final String IN6_ADDR_ANY = "::0";

	/**
	 * A string containing the "any" local address for IPv4.
	 */
	public static final String IN4_ADDR_ANY = "0.0.0.0";

	/**
	 * A string containing the "any" local address.
	 */
	public static final String IN_ADDR_ANY = determineAnyAddress();

	/**
	 * The maximum int value that could correspond to a port nubmer.
	 */
	public static final int MAX_PORT_NUMBER = 65535;

	/**
	 * The minimum int value that could correspond to a port nubmer bindable by
	 * the SIP Communicator.
	 */
	public static final int MIN_PORT_NUMBER = 1024;

	/**
	 * The random port number generator that we use in getRandomPortNumer()
	 */
	private static Random portNumberGenerator = new Random();

	/**
	 * Returns a random local port number that user applications could bind to.
	 * (i.e. above 1024).
	 * 
	 * @return a random int located between 1024 and 65 535.
	 */
	public static int getRandomPortNumber() {
		return getRandomPortNumber(MIN_PORT_NUMBER, MAX_PORT_NUMBER);
	}

	/**
	 * Returns a random local port number, greater than min and lower than max.
	 * 
	 * @param min
	 *            the minimum allowed value for the returned port number.
	 * @param max
	 *            the maximum allowed value for the returned port number.
	 * 
	 * @return a random int located between greater than min and lower than max.
	 */
	public static int getRandomPortNumber(int min, int max) {
		return portNumberGenerator.nextInt(max - min) + min;
	}

	/**
	 * Tries to determine if this host supports IPv6 addresses (i.e. has at
	 * least one IPv6 address) and returns IN6_ADDR_ANY or IN4_ADDR_ANY
	 * accordingly. This method is only used to initialize IN_ADDR_ANY so that
	 * it could be used when binding sockets. The reason we need it is because
	 * on mac (contrary to lin or win) binding a socket on 0.0.0.0 would make it
	 * deaf to IPv6 traffic. Binding on ::0 does the trick but that would fail
	 * on hosts that have no IPv6 support. Using the result of this method
	 * provides an easy way to bind sockets in cases where we simply want any IP
	 * packets coming on the port we are listening on (regardless of IP
	 * version).
	 * 
	 * @return IN6_ADDR_ANY or IN4_ADDR_ANY if this host supports or not IPv6.
	 */
	private static String determineAnyAddress() {
		Enumeration<NetworkInterface> ifaces;
		try {
			ifaces = NetworkInterface.getNetworkInterfaces();
		} catch (SocketException e) {
			logger.debug("Couldn't retrieve local interfaces.", e);
			return IN4_ADDR_ANY;
		}

		while (ifaces.hasMoreElements()) {
			Enumeration<InetAddress> addrs = ifaces.nextElement()
					.getInetAddresses();
			while (addrs.hasMoreElements()) {
				if (addrs.nextElement() instanceof Inet6Address)
					return IN6_ADDR_ANY;
			}
		}

		return IN4_ADDR_ANY;
	}

	/**
	 * Determines whether <tt>port</tt> is a valid port number bindable by an
	 * application (i.e. an integer between 1024 and 65535).
	 * 
	 * @param port
	 *            the port number that we'd like verified.
	 * 
	 * @return <tt>true</tt> if port is a valid and bindable port number and
	 *         <tt>alse</tt> otherwise.
	 */
	public static boolean isValidPortNumber(int port) {
		return MIN_PORT_NUMBER < port && port < MAX_PORT_NUMBER;

	}
}
